﻿using System;
using System.IO;
using Autofac.Extras.Moq;
using CSharpLogger.Common;
using Moq;
using Xunit;

namespace CSharpLogger.UnitTest
{
    /// <summary>
    /// Tests <see cref="ILoggerHandler"/> implementation as a <see cref="FileLogger"/>
    /// </summary>
    public class FileLoggerUnitTest : CSharpLoggerUnitTestBase
    {
        /// <summary>
        /// Gets or sets <see cref="ILoggerHandlerFactory"/> concrete implementation
        /// </summary>
        ILoggerHandlerFactory handler;

        /// <summary>
        /// Gets or sets <see cref="ILoggerHandler"/> concrete implementation
        /// </summary>
        FileLogger logger;

        /// <summary>
        /// Initializes test configuration
        /// </summary>
        public FileLoggerUnitTest()
        {
            base.Init();

            handler = new LoggerHandlerFactory();
            logger = handler.GetHandler<FileLogger>();

        }

        /// <summary>
        /// Tests message logging using default values
        /// </summary>
        [Fact]
        public void LogDefaultValuesTest()
        {

            //arrange            
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            var mock = new Mock<IFileWrapper>();
            mock.Setup(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()));

            logger.FileWrapper = mock.Object;

            //act
            logger.Log("Hello CSharpLogger", LogLevel.INFO);

            //assert 
            mock.Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Once);

        }

        /// <summary>
        /// Tests we can use custom path and file name for logging.
        /// </summary>
        [Fact]
        public void LogUsingCustomPathAdnFileName()
        {
            //arrange
            var path = @"c:\foo\";
            var filename = "bar.log";

            //act
            logger.Path = path;
            logger.FileName = filename;

            //assert
            Assert.Equal(string.Concat(logger.Path, logger.FileName), string.Concat(path, filename));

        }

        /// <summary>
        /// Tests message logging using <see cref="LogLevel.DEBUG"/>
        /// </summary>
        [Fact]
        public void LogLevelDebugTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            var mock = new Mock<IFileWrapper>();
            mock.Setup(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()));

            logger.FileWrapper = mock.Object;

            //act
            logger.Log("Hello CSharpLogger", LogLevel.DEBUG);
            logger.Log("Hello CSharpLogger", LogLevel.WARN);

            //assert 
            mock.Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Exactly(2));
        }

        /// <summary>
        /// Tests message logging using <see cref="LogLevel.WARN"/>
        /// </summary>
        [Fact]
        public void LogLevelWarnTest()
        {
            using (var mock = AutoMock.GetLoose())
            {
                //arrange
                CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.WARN);

                //act
                logger.Log("Hello CSharpLogger", LogLevel.DEBUG);
                logger.Log("Hello CSharpLogger", LogLevel.WARN);
                logger.Log("Hello CSharpLogger", LogLevel.ERROR);

                //assert 
                mock.Mock<IFileWrapper>().Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Exactly(2));
            }
        }

        /// <summary>
        /// Tests message logging using <see cref="LogLevel.ERROR"/>
        /// </summary>
        [Fact]
        public void LogLevelErrorTest()
        {
            using (var mock = AutoMock.GetLoose())
            {
                //arrange
                CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.ERROR);

                //act
                logger.Log("Hello CSharpLogger", LogLevel.FATAL);
                logger.Log("Hello CSharpLogger", LogLevel.ERROR);
                logger.Log("Hello CSharpLogger", LogLevel.WARN);

                //assert 
                mock.Mock<IFileWrapper>().Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Exactly(2));
            }
        }

        /// <summary>
        /// Tests message logging using <see cref="LogLevel.FATAL"/>
        /// </summary>
        [Fact]
        public void LogLevelFatalTest()
        {
            using (var mock = AutoMock.GetLoose())
            {
                //arrange
                CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.FATAL);

                //act
                logger.Log("Hello CSharpLogger", LogLevel.INFO);
                logger.Log("Hello CSharpLogger", LogLevel.FATAL);
                logger.Log("Hello CSharpLogger", LogLevel.ERROR);

                //assert 
                mock.Mock<IFileWrapper>().Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Exactly(2));
            }
        }

        /// <summary>
        /// Tests message logging using <see cref="LogLevel.INFO"/>
        /// </summary>
        [Fact]
        public void LogLevelinfoTest()
        {
            using (var mock = AutoMock.GetLoose())
            {
                //arrange
                CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.INFO);

                //act
                logger.Log("Hello CSharpLogger", LogLevel.INFO);
                logger.Log("Hello CSharpLogger", LogLevel.FATAL);

                //assert 
                mock.Mock<IFileWrapper>().Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Once);
            }
        }

        /// <summary>
        /// Tests message logging using <see cref="LogLevel.OFF"/>
        /// </summary>
        [Fact]
        public void LogLevelOffTest()
        {
            using (var mock = AutoMock.GetLoose())
            {
                //arrange
                CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.OFF);

                //act
                logger.Log("Hello CSharpLogger", LogLevel.INFO);
                logger.Log("Hello CSharpLogger", LogLevel.FATAL);
                logger.Log("Hello CSharpLogger", LogLevel.ERROR);
                logger.Log("Hello CSharpLogger", LogLevel.WARN);
                logger.Log("Hello CSharpLogger", LogLevel.DEBUG);

                //assert 
                mock.Mock<IFileWrapper>().Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Never);
            }
        }

        /// <summary>
        /// Tests <see cref="ArgumentNullException"/> is throwed
        /// </summary>
        [Fact]
        public void LogThrowsArgumentNullExceptionTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            var mock = new Mock<IFileWrapper>();
            mock.Setup(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>())).Throws<ArgumentNullException>();

            logger.FileWrapper = mock.Object;

            //act
            var ex = Assert.Throws<ArgumentNullException>(() => logger.Log("Hello CSharpLogger"));

            //assert 
            mock.Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Once);

            Assert.Equal<string>("Value cannot be null.", ex.Message);
        }

        /// <summary>
        /// Tests <see cref="IOException"/> is throwed
        /// </summary>
        [Fact]
        public void LogThrowsIOExceptionTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            var mock = new Mock<IFileWrapper>();
            mock.Setup(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>())).Throws<IOException>();

            logger.FileWrapper = mock.Object;

            //act
            var ex = Assert.Throws<IOException>(() => logger.Log("Hello CSharpLogger"));

            //assert 
            mock.Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Once);

            Assert.Equal<string>("I/O error occurred.", ex.Message);
        }

        /// <summary>
        /// Tests <see cref="IOException"/> is throwed
        /// </summary>
        [Fact]
        public void LogThrowsFormatExceptionTest()
        {
            //arrange
            CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG);

            var mock = new Mock<IFileWrapper>();
            mock.Setup(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()))
                .Callback(() =>
            {
                throw new CSharpLoggerException("FileWrapper.WriteLog", new FormatException());
            });

            logger.FileWrapper = mock.Object;

            //act
            var ex = Assert.Throws<CSharpLoggerException>(() => logger.Log("Hello CSharpLogger"));

            //assert 
            mock.Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Once);

            Assert.Equal<string>("One of the identified items was in an invalid format.", ex.InnerException.Message);
        }
    }
}